#include "StorageDevice.h"

//------------------------------------------------------------------------------
#define BOOTSIG0 0X55
#define BOOTSIG1 0XAA

// End Of Chain values for FAT entries
#define FAT16EOC     0XFFFF
#define FAT16EOC_MIN 0XFFF8
#define FAT32EOC     0X0FFFFFFF
#define FAT32EOC_MIN 0X0FFFFFF8
#define FAT32MASK    0X0FFFFFFF

#define CACHE_FOR_READ   0
#define CACHE_FOR_WRITE  1

#define CACHE_BLOCK_INVALID 0XFFFFFFFF

#define NULL 0

struct part_t {
  char boot;
  char beginHead;
  char beginCylinderHigh_beginSector;//2:6
  char beginCylinderLow;
  char type;
  char endHead;
  char endCylinderHigh_endSector;//2:6
  char endCylinderLow;
  char firstSector_0;
  char firstSector_1;
  char firstSector_2;
  char firstSector_3;
  char totalSectors_0;
  char totalSectors_1;
  char totalSectors_2;
  char totalSectors_3;
};

struct mbr_t {
  char  codeArea[440];
  char diskSignature_0;
  char diskSignature_1;
  char diskSignature_2;
  char diskSignature_3;
  char usuallyZero_0;
  char usuallyZero_1;
  part_t part[4];
  char mbrSig0;
  char mbrSig1;
};

struct dir_t {
  char name[11];
  char attributes;
  char reservedNT;
  char creationTimeTenths;
  char creationTime_0;
  char creationTime_1;
  char creationDate_0;
  char creationDate_1;
  char lastAccessDate_0;
  char lastAccessDate_1;
  char firstClusterHigh_0;
  char firstClusterHigh_1;
  char lastWriteTime_0;
  char lastWriteTime_1;
  char lastWriteDate_0;
  char lastWriteDate_1;
  char firstClusterLow_0;
  char firstClusterLow_1;
  char fileSize_0;
  char fileSize_1;
  char fileSize_2;
  char fileSize_3;
};

struct bpb_t {
  char bytesPerSector_0;
  char bytesPerSector_1;
  char sectorsPerCluster;
  char reservedSectorCount_0;
  char reservedSectorCount_1;
  char fatCount;
  char rootDirEntryCount_0;
  char rootDirEntryCount_1;
  char totalSectors16_0;
  char totalSectors16_1;
  char mediaType;
  char sectorsPerFat16_0;
  char sectorsPerFat16_1;
  char sectorsPerTrtack_0;
  char sectorsPerTrtack_1;
  char headCount_0;
  char headCount_1;
  char hidddenSectors_0;
  char hidddenSectors_1;
  char hidddenSectors_2;
  char hidddenSectors_3;
  char totalSectors32_0;
  char totalSectors32_1;
  char totalSectors32_2;
  char totalSectors32_3;
  char sectorsPerFat32_0;
  char sectorsPerFat32_1;
  char sectorsPerFat32_2;
  char sectorsPerFat32_3;
  char fat32Flags_0;
  char fat32Flags_1;
  char fat32Version_0;
  char fat32Version_1;
  char fat32RootCluster_0;
  char fat32RootCluster_1;
  char fat32RootCluster_2;
  char fat32RootCluster_3;
  char fat32FSInfo_0;
  char fat32FSInfo_1;
  char fat32BackBootBlock_0;
  char fat32BackBootBlock_1;
  char fat32Reserved[12];
};

struct fbs_t {
  char jmpToBootCode[3];
  char oemName[8];
  bpb_t bpb;
  char driveNumber;
  char reserved1;
  char bootSignature;
  char volumeSerialNumber_0;
  char volumeSerialNumber_1;
  char volumeSerialNumber_2;
  char volumeSerialNumber_3;
  char volumeLabel[11];
  char fileSystemType[8];
  char bootCode[420];
  char bootSectorSig0;
  char bootSectorSig1;
};

union cache_t {
  char  data[512];
  short fat16[256];
  int fat32[128];
  dir_t    dir[16];
  mbr_t    mbr;
  fbs_t    fbs;
};

struct cache_entity {
  cache_t cacheBuffer;
  int cacheDirty;
  int cacheBlockNumber;
  int cacheLifeCount;
  int cacheMirrorBlock;
};

class SdFile;

#define CACHE_COUNT 8
class SdVolume {
 public:

  cache_entity cacheEntity[CACHE_COUNT];
  
  int allocSearchStart_;   // start cluster for alloc search
  int blocksPerCluster_;    // cluster size in blocks
  int blocksPerFat_;       // FAT size in blocks
  int clusterCount_;       // clusters in one FAT
  int clusterSizeShift_;    // shift to convert cluster count to block count
  int dataStartBlock_;     // first data block number
  int fatCount_;            // number of FATs on volume
  int fatStartBlock_;      // start block for first FAT
  int fatType_;             // volume type (12, 16, OR 32)
  int rootDirEntryCount_;  // number of entries in FAT16 root dir
  int rootDirStart_;       // root start block for FAT16, cluster for FAT32

  SdFile* root;
  
  int error;
  
  StorageDevice* sdCard_;
  StorageDevice* sdCard(void);

  int initError;
  cache_entity* errorSector;
  int printErrorSector();
  
  int init(StorageDevice* dev, int part);

// find a contiguous group of clusters
  int allocContiguous(int count, int* curCluster);
  
  int blockOfCluster(int position);
  int clusterStartBlock(int cluster);
  int blockNumber(int cluster, int position);

  int cacheFlushAll();
  void cacheReport();

  int cacheFlush(cache_entity* cache);

  int cacheRawBlock(int blockNumber, int action, cache_entity** cacheBuffer_, int isZero, int isFat, int ID);
  
  void cacheSetDirty(cache_entity* cacheBuffer_);
  
  // cache a zero block for blockNumber
  int cacheZeroBlock(int blockNumber);
  
  // return the size in bytes of a cluster chain
  int chainSize(int cluster, int* size);

  // Fetch a FAT entry
  int fatGet(int cluster, int* next);

  int fatPutError;
  // Store a FAT entry
  int fatPut(int cluster, int next);
  
  int fatPutEOC(int cluster);
  
  int freeChainError;
  // free a cluster chain
  int freeChain(int cluster);
  
  int isEOC(int cluster);

};


// int SdVolume::arrayaa[] = {1,2,3,4,5};

// flags for ls()

#define LS_DATE  1 /** ls() flag to print modify date */
#define LS_SIZE  2 /** ls() flag to print file size */
#define LS_R     4 /** ls() flag for recursive list of subdirectories */

// use the gnu style oflag in open()

#define O_READ      0X01               /** open() oflag for reading */
#define O_RDONLY    O_READ             /** open() oflag - same as O_READ */
#define O_WRITE     0X02               /** open() oflag for write */
#define O_WRONLY    O_WRITE            /** open() oflag - same as O_WRITE */
#define O_RDWR      (O_READ | O_WRITE) /** open() oflag for reading and writing */
#define O_ACCMODE   (O_READ | O_WRITE) /** open() oflag mask for access modes */
#define O_APPEND    0X04               /** The file offset shall be set to the end of the file prior to each write. */
#define O_SYNC      0X08               /** synchronous writes - call sync() after each write */
#define O_CREAT     0X10               /** create the file if nonexistent */
#define O_EXCL      0X20               /** If O_CREAT and O_EXCL are set, open() shall fail if the file exists */
#define O_TRUNC     0X40               /** truncate the file to zero length */

// flags for timestamp

#define T_ACCESS              1                     /** set the file's last access date */
#define T_CREATE              2                     /** set the file's creation date and time */
#define T_WRITE               4                     /** Set the file's write date and time */
#define FAT_FILE_TYPE_CLOSED  0                     /** This SdFile has not been opened. */
#define FAT_FILE_TYPE_NORMAL  1                     /** SdFile for a file */
#define FAT_FILE_TYPE_ROOT16  2                     /** SdFile for a FAT16 root directory */
#define FAT_FILE_TYPE_ROOT32  3                     /** SdFile for a FAT32 root directory */
#define FAT_FILE_TYPE_SUBDIR  4                     /** SdFile for a subdirectory */
#define FAT_FILE_TYPE_MIN_DIR FAT_FILE_TYPE_ROOT16  /** Test value for directory type */

// bits defined in flags_
// should be 0XF
#define F_OFLAG   (O_ACCMODE | O_APPEND | O_SYNC)
// available bits
#define F_UNUSED  0X30
// use unbuffered SD read
//#define F_FILE_UNBUFFERED_READ  0X40
// sync of directory entry required
#define F_FILE_DIR_DIRTY        0X80

#define DIR_NAME_DELETED        '\xE5'
#define DIR_NAME_FREE           '\0'
#define DIR_ATT_READ_ONLY       0X01
#define DIR_ATT_HIDDEN          0X02
#define DIR_ATT_SYSTEM          0X04
#define DIR_ATT_VOLUME_ID       0X08
#define DIR_ATT_DIRECTORY       0X10
#define DIR_ATT_ARCHIVE         0X20
#define DIR_ATT_LONG_NAME       0X0F
#define DIR_ATT_LONG_NAME_MASK  0X3F
#define DIR_ATT_DEFINED_BITS    0X3F
#define DIR_ATT_FILE_TYPE_MASK  (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY)



class SdFile {
 public:
  // private data
  int  flags_;         // See above for definition of flags_ bits
  int  type_;          // type of file see above for values
  int  curCluster_;    // cluster for current file position
  int  curPosition_;   // current file position in bytes from beginning
  int  dirBlock_;      // SD block that contains directory entry for file
  int  dirIndex_;      // index of entry in dirBlock 0 <= dirIndex_ <= 0XF
  int  fileSize_;      // file size in bytes
  int  firstCluster_;  // first cluster of file
  
  cache_entity* cacheBuffer_;
  SdVolume* vol_;           // volume where file is located
  /** Create an instance of SdFile. */
  int fileError;
  /**
   * writeError is set to true if an error occurs during a write().
   * Set writeError to false before calling print() and/or write() and check
   * for true after calls to print() and/or write().
   */
  //bool writeError;

  int close(void);
  
  /** \return True if this is a SdFile for a directory else false. */
  int isDir(void);
  /** \return True if this is a SdFile for a file else false. */
  int isFile(void);
  /** \return True if this is a SdFile for an open file/directory else false. */
  int isOpen(void);
  /** \return True if this is a SdFile for a subdirectory else false. */
  int isSubDir(void);
  /** \return True if this is a SdFile for the root directory. */
  int isRoot(void);
  int makeDir(SdFile* parentDir, const char* dirName);
  
  int open(SdFile* dirFile, const char* fileName, int oflag);

  int openRoot(SdVolume* vol);
  
  
//------------------------------------------------------------------------------
/** List directory contents to Serial.
 *
 * \param[in] flags The inclusive OR of
 *
 * LS_DATE - %Print file modification date
 *
 * LS_SIZE - %Print file size.
 *
 * LS_R - Recursive list of subdirectories.
 *
 * \param[in] indent Amount of space before file name. Used for recursive
 * list to indicate subdirectory level.
 */
  void dirList(int, int);
  void dirList(void*, int(*)(void*, char*, int));
  
  /**
   * Read the next byte from a file.
   *
   * \return For success read returns the next byte in the file as an int.
   * If an error occurs or end of file is reached -1 is returned.
   */
  int read();

  int read(char* buf, int nbyte);

  int readDir(dir_t* dir);
  
  int delError;
  int del(SdFile* dirFile, const char* fileName);
  
  int removeError;
  int remove(void);
  
  /** Set the file's current position to zero. */
  void rewind(void);
  int rmDir(void);
  
  int rmRfStar(void);
  /** Set the files position to current position + \a pos. See seekSet(). */
  int seekCur(int pos);
  /**
   *  Set the files current position to end of file.  Useful to position
   *  a file for append. See seekSet().
   */
  int seekEnd(void);
  /**
   * Sets a file's position.
   *
   * \param[in] pos The new position in bytes from the beginning of the file.
   *
   * \return The value one, true, is returned for success and
   * the value zero, false, is returned for failure.
   */
  int seekSet(int pos);
  
  

//  uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day,
//          uint8_t hour, uint8_t minute, uint8_t second);
  /**
   * The sync() call causes all modified data and directory fields
   * to be written to the storage device.
   *
   * \return The value one, true, is returned for success and
   * the value zero, false, is returned for failure.
   * Reasons for failure include a call to sync() before a file has been
   * opened or an I/O error.
   */
  int sync(void);
  
/**
 * Truncate a file to a specified length.  The current file position
 * will be maintained if it is less than or equal to \a length otherwise
 * it will be set to end of file.
 *
 * \param[in] length The desired length for the file.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include file is read only, file is a directory,
 * \a length is greater than the current file size or an I/O error occurs.
 */
  int truncateError;
  int truncate(int length);
  
  /** \return SdVolume that contains this file. */
  SdVolume* volume(void);
  
  int write(int b);
  //size_t write(const void* buf, uint16_t nbyte);
  int write(const void* buf, int nbyte);


  // private functions
  // add a cluster to a file
  int addCluster();

  // Add a cluster to a directory file and zero the cluster.
  // return with first block of cluster in the cache
  int addDirCluster(void);
  // cache a file's directory entry
  // return pointer to cached entry or null for failure
  dir_t* cacheDirEntry();

//  static void (*dateTime_)(uint16_t* date, uint16_t* time);

//------------------------------------------------------------------------------
// format directory name field from a 8.3 name string
  int make83Name(const char* str, char* name);
    
  // open a cached directory entry. Assumes vol_ is initializes
  int openCachedEntry(int dirIndex, int oflag, SdFile* dirFile);
  
  // Read next directory entry into the cache
  // Assumes file is correctly positioned
  dir_t* readDirCache(void);

};
